#  1. Set up the Virgo project folder like the following:
#      * indicate the folder must be created manually
#    projRoot\                  #* any name, letters and numbers, no spaces
#    projRoot\                  #* any name, letters and numbers, no spaces
#      install\                 #*
#         win64\                #  install to this folder or linux64_opt linux64_dbg
#      sources\                 #*
#         vgsolver\             #* Virgo solver source code from svn repository
#            buildScripts\      #  where this script lives
#            trunk\             #
#      builds\                  #*
#         vgsolver              #  solver build folder 
#
#  2. Run this script from Visual Studio (x64 Native) Command Prompt
#     VS version (2015 or 2019) is detected automatically
#      cd projRoot
#      python3 .\sources\vgsolver\buildScripts\buildVirgo3.py [options]
#        options:
#           debug   #[release, coverage, coverage_release]  build mode [default release]
#           --Comp  #components: all, pam, fea, multife, rkpm, bem, solver, copydll
#                                default all = pam, rkpm, multife, solver
#

import os, os.path, glob, fnmatch, subprocess, sys
import argparse, filecmp, shutil
from datetime import datetime
from datetime import date

myVersion = "v2.16"

# 检查文件的函数
def check_file(cf):
    if not os.path.isfile(cf):
        print("")
        print("FAIL: cannot find file ", cf)
        sys.exit("Aborted!")

# 判断是否是一个路径
def check_dir(cdir):
    if not os.path.exists(cdir):
        print("")
        print("FAIL: cannot find folder ", cdir)
        sys.exit("Aborted!")

#returns true if myname is contained in components to build
def build_me(components, myname):
    if "all" in components or myname in components:
        return True
    return False

#main entry
if __name__ == '__main__': # 因此通过判断__name__的值，就可以区分py文件是直接被运行，还是被引入其他程序中 https://blog.csdn.net/wosind/article/details/90728198
    print("\nStarting xxx project build script ", myVersion)
    print(str(datetime.now())) # 打印现在时间

    scriptDir = os.path.dirname(os.path.realpath(__file__)) # 给出脚本的路径
    # projRoot = os.path.abspath(os.path.join(scriptDir, "..", "..", "..")) # 给出项目的路径，所以，脚本和项目的相对位置要设置好
    projRoot = os.path.abspath(os.path.join(scriptDir)) # 给出项目的路径，所以，脚本和项目的相对位置要设置好

    parser = argparse.ArgumentParser() # 解析命令
    #positional argument
    parser.add_argument('type', help='specify build type as Release or Debug')
    #optional argument, solver solver has just 1 component
    parser.add_argument('Comp', help='specify the component to build')
    args = parser.parse_args()
    
    components = "all"
    # 决定要build的项目名称，由程序输入的命令行确定
    if isinstance(args.Comp, str): # isinstance() 函数来判断一个对象是否是一个已知的类型，类似 type()。
        components = args.Comp.lower()

    # 确定build的类型
    buildType="Release"
    if (args.type == "Release") or (args.type == "release"):
        buildType = "Release"
    elif (args.type == "Debug") or (args.type == "debug"):
        buildType = "Debug"
    elif (args.type == "Coverage") or (args.type == "coverage"):
        buildType = "Coverage"
    elif (args.type == "Coverage_Release") or (args.type == "coverage_release"):
        buildType = "Coverage_Release"
    else:
        print("Error: ")
        print("  Unrecognized build type " + args.type)
        sys.exit("Aborted!")
    
    try:
        
        # 这个方法的作用是执行一些命令行的命令，例如 sh xxx.sh , java -jar xxx.jar 等，(相当于执行shell命令？)
        # 会开启一个子进程去执行，并且等待子进程结束才继续执行其他的，使用起来非常方便，就是需要注意一些小细节
        subprocess.call(['cmake', '--version']) # subprocess 模块允许我们启动一个新进程，并连接到它们的输入/输出/错误管道，从而获取返回值。
    except OSError:
        print("")
        print("Error: ")
        print("  Cannot find cmake")
        sys.exit("Aborted!")
    
    # 给出一些变量定义
    pamBuildDir = "pam"
    feaBuildDir = "fea"
    mfeBuildDir = "multife"
    rkpmBuildDir = "rkpm"
    bemBuildDir = "bem"
    solverBuildDir = "vgsolver"
    archSLTN = ""
    if os.name == "posix": # posix指的是linux/MAC
        if buildType == "Release":
            suffix = "_opt" # 后缀
        elif buildType == "Debug":
            suffix = "_dbg"
        elif buildType == "Coverage":
            suffix = "_dbg_cov"
        else:
            suffix = "_opt_cov"

        installSubDir = "linux64" + suffix # 给出install目录
        solverBuildDir = solverBuildDir + suffix # 给出
        rkpmBuildDir = rkpmBuildDir + suffix
        bemBuildDir = bemBuildDir + suffix
        pamBuildDir = pamBuildDir + suffix
        feaBuildDir = feaBuildDir + suffix
        mfeBuildDir = mfeBuildDir + suffix
        # install3p = "/devread/3p/linux64" # 给出依赖的位置
        install3p =os.path.join(projRoot, "3p") 
        install2p = "/devread/2p/linux64"
        buildInstallCMD = "make -j4 install" # 给出build的脚本命令
        buildTestCMD = "make tests" # 给出test的命令
        cmakeSLTN = "Unix Makefiles" # cmake使用的generator类型
        #compilers
        #compilerPath = "/usr/local"      #gcc 6.1
        compilerPath = "/usr/gcc/9.3.0"   #gcc 9.3 #给出gcc路径
        cCompiler = compilerPath + "/bin/gcc" # gcc位置
        cxxCompiler = compilerPath + "/bin/g++"# g++位置
        fCompiler = compilerPath + "/bin/gfortran" # gfortran位置
        stdlibPath = compilerPath + "/lib64" # lib位置
        makeCmd = "/usr/local/bin/make" # make的位置
        if not os.path.isfile(makeCmd):# 如果make不再‘makeCmd’中的话
            makeCmd = "/usr/bin/make" # 认为其在此处
        check_file(makeCmd) # 判断其是不是file
        os.environ["LD_LIBRARY_PATH"] = stdlibPath + ":" + os.environ["LD_LIBRARY_PATH"]# LIB的路径，为给出的gcc路径‘stdlibPath’和系统的lib路径
        
    else:
        if "Coverage" in buildType:
            print("Error:")
            print("  Coverage is not supported on Windows.")
            sys.exit("Aborted!")

        #visual studio compilers on windows
        installSubDir = "win64"
        #need \\ below
        install3p = os.path.join("Z:\\3p", "win64")
        install2p = os.path.join("Z:\\2p", "win64")
        buildInstallCMD = "cmake --build . --config " + buildType + " --target INSTALL"
        buildTestCMD = "cmake --build . --config " + buildType + " --target tests"
        
        if os.environ['VisualStudioVersion'] == "14.0":
            cmakeSLTN="Visual Studio 14 2015 Win64"
        elif os.environ['VisualStudioVersion'] == "12.0":
            cmakeSLTN="Visual Studio 12 2013 Win64"
        elif os.environ['VisualStudioVersion'] == "16.0":
            cmakeSLTN="Visual Studio 16 2019"
            archSLTN=" -A x64"
        else:
            print("Error:")
            print("  Visual Studio environment is not loaded.")
            sys.exit("Aborted!")

    if os.name == "nt": # 表示windows系统
        if not os.path.exists("Z:\\"):
            print("Warning: Z drive doesn't exist. Trying to mount it")
            #subprocess.call(r'dir \\10.10.10.105\devread', shell=True)
            subprocess.call(r'net use Z: \\10.10.10.105\devread /PERSISTENT:Yes', shell=True)
            
        if not os.path.exists(r'Z:'):
            print("Warning: Z drive still doesn't exist")

    cmakeSLTN = "\"" + cmakeSLTN + "\"" + archSLTN # 添加双引号
    installDir = os.path.join(projRoot, "install") # 添加install目录
    buildDir = os.path.join(projRoot, "builds") # 添加builds目录
    sourceDir = os.path.join(projRoot, "sources") # 确定源文件目录
    solverSourceDir = os.path.join(sourceDir, "source1")
    pamSourceDir = os.path.join(sourceDir, "pam", "sources", "api")
    rkpmSourceDir = os.path.join(sourceDir, "rkpm", "trunk")
    feaSourceDir = os.path.join(sourceDir, "fea", "trunk")
    mfeSourceDir = os.path.join(sourceDir, "multife", "trunk")
    bemSourceDir = os.path.join(sourceDir, "bem", "trunk")

    # 检查目录，这些目录需要提前创建好
    check_dir(install3p)
    check_dir(buildDir)
    check_dir(installDir)
    check_dir(sourceDir)
    check_dir(solverSourceDir)

    print("")
    print("----------------------------------------------")  
    print("project root dir: ", projRoot)
    print("build type      : ", buildType)
    print("install dir     : ", installDir)
    print("install 3p      : ", install3p)
    
    #out_file.write(solverVersion)
    currDir = os.getcwd()

    # 给出boost库目录
    boost_dir=os.path.join(install3p, "boost", "1.72")
    # 给出tinyxml库
    tinyxml2_dir=os.path.join(install3p, "tinyxml2")
    if os.name == "posix":
        petsc_dir=os.path.join(install3p, "petsc")
        slepc_dir=os.path.join(install3p, "slepc")
        mpi_dir=os.path.join(install3p, "openmpi")
    else:
        petsc_dir=os.path.join(install3p, "petsc", "3.9.3-static-no-mpi")
        slepc_dir=os.path.join(install3p, "slepc", "3.9.2-static-no-mpi")
        #make sure msmpi is installed at the default location
        mpi_lib = os.path.join("C:\\", "Program Files (x86)", "Microsoft SDKs")
        mpi_lib = os.path.join(mpi_lib, "MPI", "Lib", "x64", "msmpi.lib")
        check_file(mpi_lib)
        mpi_dir = r'"C:\Program Files (x86)\Microsoft SDKs\MPI"'
        mpi_lib = os.path.join(mpi_dir, "Lib", "x64", "msmpi.lib")

    actualInstall = os.path.join(installDir, installSubDir)
    configCMD = "cmake -D CMAKE_INSTALL_PREFIX:PATH=" + actualInstall
    if os.name == "posix":
        configCMD = configCMD + " -D CMAKE_BUILD_TYPE:STRING=" + buildType # build类型
        configCMD = configCMD + " -D CMAKE_C_COMPILER:PATH=" + cCompiler # c编译器位置 
        configCMD = configCMD + " -D CMAKE_CXX_COMPILER:PATH=" + cxxCompiler # c++编译器位置

    #-------------------------------------------------------------
    #build Plexian Analysis Module only if it exists
    os.chdir(buildDir)
    if build_me(components, "pam"):
        if not (os.path.exists(pamSourceDir)):
            sys.exit("pam source folder does not exist!")

        if os.path.exists(pamBuildDir):
            print()
            print("Warning: folder " + pamBuildDir + " exists, deleting")
            print()
            shutil.rmtree(pamBuildDir)
        os.makedirs(pamBuildDir)
        os.chdir(pamBuildDir)

        pamCMD = configCMD
        pamCMD = pamCMD + " -D AD3p_Dir:PATH=" + install3p
        pamCMD = pamCMD + " -G " + cmakeSLTN
        pamCMD = pamCMD + " " + pamSourceDir
        print("------------")
        subprocess.call(pamCMD, shell=True)
        print("------------")
        subprocess.call(buildInstallCMD, shell=True)

    #-------------------------------------------------------------
    #build bem component (not build by default or all)
    os.chdir(buildDir)
    if "bem" in components:
        if not os.path.exists(bemSourceDir):
            sys.exit("bem source folder does not exist!")

        if os.path.exists(bemBuildDir):
            print()
            print("Warning: folder " + bemBuildDir + " exists, deleting")
            print()
            shutil.rmtree(bemBuildDir)
        os.makedirs(bemBuildDir)
        os.chdir(bemBuildDir)

        bmCMD = configCMD
        bmCMD = bmCMD + " -D AD3p_Dir:PATH=" + install3p
        bmCMD = bmCMD + " -G " + cmakeSLTN
        bmCMD = bmCMD + " " + bemSourceDir
    
        #print "------------"
        #print bmCMD
        subprocess.call(bmCMD, shell=True)
    
        print("------------")
        print(buildInstallCMD)
        subprocess.call(buildInstallCMD, shell=True)
    
        #return to the original directory 
        os.chdir(currDir)

    #-------------------------------------------------------------
    #build rkpm component
    os.chdir(buildDir)
    if build_me(components, "rkpm"):
        if not os.path.exists(rkpmSourceDir):
            sys.exit("rkpm source folder does not exist!")

        if os.path.exists(rkpmBuildDir):
            print()
            print("Warning: folder " + rkpmBuildDir + " exists, deleting")
            print()
            shutil.rmtree(rkpmBuildDir)
        os.makedirs(rkpmBuildDir)
        os.chdir(rkpmBuildDir)

        vgCMD = configCMD
        vgCMD = vgCMD + " -D AD3p_Dir:PATH=" + install3p
        if os.name == "posix":
            vgCMD = vgCMD + " -D USE_MPI:BOOL=ON"

        vgCMD = vgCMD + " -G " + cmakeSLTN
        vgCMD = vgCMD + " " + rkpmSourceDir
    
        #print "------------"
        #print vgCMD
        subprocess.call(vgCMD, shell=True)
    
        print("------------")
        print(buildInstallCMD)
        subprocess.call(buildInstallCMD, shell=True)
    
        #return to the original directory 
        os.chdir(currDir)

    #-------------------------------------------------------------
    os.chdir(buildDir)
    if "fea" in components:
        if not (os.path.exists(feaSourceDir)):
            sys.exit("fea source folder does not exist!")

        if os.path.exists(feaBuildDir):
            print()
            print("Warning: folder " + feaBuildDir + " exists, deleting")
            print()
            shutil.rmtree(feaBuildDir)
        os.makedirs(feaBuildDir)
        os.chdir(feaBuildDir)

        feaCMD = configCMD
        feaCMD = feaCMD + " -D AD3p_Dir:PATH=" + install3p
        feaCMD = feaCMD + " -D USE_LAPACK:BOOL=TRUE"
        feaCMD = feaCMD + " -D USE_PETSC:BOOL=TRUE"
        feaCMD = feaCMD + " -D MY_PETSC_DIR:PATH=" + petsc_dir
        feaCMD = feaCMD + " -D USE_SLEPC:BOOL=TRUE"
        feaCMD = feaCMD + " -D SLEPC_DIR:PATH=" + slepc_dir
        feaCMD = feaCMD + " -D USE_IML:BOOL=TRUE"
        feaCMD = feaCMD + " -D USE_DSS:BOOL=TRUE"
        feaCMD = feaCMD + " -D USE_OPENMP:BOOL=TRUE"
        if os.name == "posix":
            feaCMD = feaCMD + " -D MPI_DIR:PATH=" + mpi_dir
            feaCMD = feaCMD + " -D USE_PARALLEL:BOOL=TRUE"
            # RZ: variables MPI_CXX_COMPILER, MPI_C_COMPILER are NOT used in 
            #     the FEA project according to the CMake warning message. So
            #     I disabled the next 4 lines to suppress the warning.
            #feaCMD = feaCMD + " -D MPI_CXX_COMPILER:PATH=" + \
            #         os.path.join(mpi_dir, "bin", "mpicxx")
            #feaCMD = feaCMD + " -D MPI_C_COMPILER:PATH=" + \
            #         os.path.join(mpi_dir, "bin", "mpicc")
        else:
            feaCMD = feaCMD
            #turn off MPI
            #feaCMD = feaCMD + " -D MPI_DIR:PATH=" + mpi_dir
            #feaCMD = feaCMD + " -D MPI_LIBRARY:PATH=" + mpi_lib
            #feaCMD = feaCMD + " -D MPI_CXX_LIBRARY:PATH=" + mpi_lib

        #tinyxml is for cemhyd only, need to alter cmake file
        #feaCMD = feaCMD + " -D USE_TINYXML:BOOL=TRUE"
        feaCMD = feaCMD + " -G " + cmakeSLTN
        feaCMD = feaCMD + " " + feaSourceDir
        print("------------")
        subprocess.call(feaCMD, shell=True)
        print("------------")
        subprocess.call(buildInstallCMD, shell=True)

    #-------------------------------------------------------------
    os.chdir(buildDir)
    if build_me(components, "multife"):
        # 判断是否存在mfe的目录
        if not (os.path.exists(mfeSourceDir)):
            sys.exit("multife source folder does not exist!")

        if os.path.exists(mfeBuildDir):
            print()
            print("Warning: folder " + mfeBuildDir + " exists, deleting")
            print()
            shutil.rmtree(mfeBuildDir)
        os.makedirs(mfeBuildDir) # 创建builds的子目录
        os.chdir(mfeBuildDir) # os.chdir() ！！！方法用于改变当前工作目录到指定的路径。

        # 结合cmake的-D指令给出build的额外指令
        mfeCMD = configCMD # 给出build的命令
        mfeCMD = mfeCMD + " -D AD3p_Dir:PATH=" + install3p
        mfeCMD = mfeCMD + " -D USE_LAPACK:BOOL=TRUE"
        mfeCMD = mfeCMD + " -D USE_PETSC:BOOL=TRUE"
        mfeCMD = mfeCMD + " -D MY_PETSC_DIR:PATH=" + petsc_dir
        mfeCMD = mfeCMD + " -D USE_SLEPC:BOOL=TRUE"
        mfeCMD = mfeCMD + " -D SLEPC_DIR:PATH=" + slepc_dir
        mfeCMD = mfeCMD + " -D USE_IML:BOOL=TRUE"
        mfeCMD = mfeCMD + " -D USE_DSS:BOOL=TRUE"
        mfeCMD = mfeCMD + " -D USE_OPENMP:BOOL=TRUE"
        if os.name == "posix":
            mfeCMD = mfeCMD + " -D MPI_DIR:PATH=" + mpi_dir
            mfeCMD = mfeCMD + " -D USE_PARALLEL:BOOL=TRUE"
        else:
            mfeCMD = mfeCMD

        mfeCMD = mfeCMD + " -G " + cmakeSLTN
        mfeCMD = mfeCMD + " " + mfeSourceDir # 给出source的目录

        print("------------")
        subprocess.call(mfeCMD, shell=True) #启动shell脚本，执行mfeCMD指令，即‘cmake xxx‘指令
        print("------------")
        subprocess.call(buildInstallCMD, shell=True) # 即'make xxx'指令

    #-------------------------------------------------------------
    #build virgo solver
    os.chdir(buildDir)
    if build_me(components, "solver"):
        if not os.path.exists(solverSourceDir):
            sys.exit("solver source folder does not exist!")

        if os.path.exists(solverBuildDir):
            print()
            print("Warning: folder " + solverBuildDir + " exists, deleting")
            print()
            shutil.rmtree(solverBuildDir)
        os.makedirs(solverBuildDir)
        os.chdir(solverBuildDir)

        #----------------------
        vgCMD = configCMD
        vgCMD = vgCMD + " -D AD3p_Dir:PATH=" + install3p
        vgCMD = vgCMD + " -D AD2p_Dir:PATH=" + install2p
        vgCMD = vgCMD + " -D Have_PAM:BOOL=ON"
        vgCMD = vgCMD + " -D Have_FEA:BOOL=ON"
        if os.name == "posix":
            vgCMD = vgCMD + " -D USE_MPI:BOOL=ON"

        vgCMD = vgCMD + " -G " + cmakeSLTN
        vgCMD = vgCMD + " " + solverSourceDir
    
        #print "------------"
        #print vgCMD
        subprocess.call(vgCMD, shell=True)
    
        print("------------")
        print(buildInstallCMD)
        subprocess.call(buildInstallCMD, shell=True)

        print("------------")
        print(buildTestCMD)
        subprocess.call(buildTestCMD, shell=True)
    
        #return to the original directory 
        os.chdir(currDir)

    #-------------------------------------------------------------
    #copy dll and exe to VirgoWin64
    if "copydll" in components and os.name == "nt":
        print("\n------------")
        print("Copy dll to VirgoWin64!")
        toDir = os.path.join(installDir, "VirgoWin64", "release", "solver")
        fromDir = os.path.join(projRoot, "install", "win64", "bin")

        if not os.path.exists(toDir):
            sys.exit(toDir, " folder does not exist!")

        from shutil import copy2
        copy2(os.path.join(fromDir, "plexAns.dll"), toDir)
        copy2(os.path.join(fromDir, "adfe_base.dll"), toDir)
        copy2(os.path.join(fromDir, "adfe_dss.dll"), toDir)
        copy2(os.path.join(fromDir, "adfe_export.dll"), toDir)
        copy2(os.path.join(fromDir, "adfe_fm.dll"), toDir)
        copy2(os.path.join(fromDir, "adfe_main.dll"), toDir)
        copy2(os.path.join(fromDir, "adfe_model.dll"), toDir)
        copy2(os.path.join(fromDir, "adfe_sm.dll"), toDir)
        copy2(os.path.join(fromDir, "adfe_sm_fm.dll"), toDir)
        copy2(os.path.join(fromDir, "adfe_tm.dll"), toDir)
        copy2(os.path.join(fromDir, "adfe_tm_fm.dll"), toDir)
        copy2(os.path.join(fromDir, "libmultisp.dll"), toDir)
        copy2(os.path.join(fromDir, "interp.dll"), toDir)
        copy2(os.path.join(fromDir, "geom_base.dll"), toDir)
        copy2(os.path.join(fromDir, "algb.dll"), toDir)
        copy2(os.path.join(fromDir, "rkpm_kernel.dll"), toDir)
        copy2(os.path.join(fromDir, "fem_kernel.dll"), toDir)
        copy2(os.path.join(fromDir, "vg_model.dll"), toDir)
        copy2(os.path.join(fromDir, "vg_solver.exe"), toDir)
        copy2(os.path.join(fromDir, "alta_tool.dll"), toDir)
        print("------------")


    #print summary on screen
    print("")
    print("----------------------------------------------")
    print("Build completed!")
    print(str(datetime.now()))
